<?php
/**
 * Represents a Sleek Site
 */
class Site {
	/**
	 * Holds the primary site name
	 *
	 * @var string $site
	 */
	private $site;

	/**
	 * Holds all the site names this site consists of
	 *
	 * @var array $sites
	 */
	private $sites;

	/**
	 * Holds the data grabbed from SiteName.info-file
	 *
	 * @var array $info
	 */
	private $info;

	/**
	 * Builds the new site based $site
	 *
	 * @param string $site
	 */
	public function __construct ($site) {
		$site = basename($site);

		if (!$this->siteExists($site)) {
			throw new Exception("Primary site \"$site\" as defined in " . WEBROOT . "index.php does not exist.<br/>Make sure you have created " . WEBROOT . "Sites/$site/ as well as " . WEBROOT . "Sites/$site/$site.info.");
		}

		$this->site = $site;

		# Find out which sites this $site extends through its mandatory SiteName.info-file
		$this->grabInfoFileData();

		# Make sure all extended sites exist
		$sites		= array_merge(array($this->site), (isset($this->info['extends']) ? (array)$this->info['extends'] : array()));
		$validSites	= array();

		foreach ($sites as $site) {
			$site = basename($site);

			if ($this->siteExists($site)) {
				$validSites[] = $site;
			}
			else {
				throw new Exception("Extended site \"$site\" does not exist in " . WEBROOT . "Sites/{$this->site}/{$this->site}.info.<br/>Have you made a typo?");
			}
		}

		$this->sites = $validSites;

		# Include all plugins for this site
		$this->includePlugins();
	}

	/**
	 * Finds out whether $site exists
	 *
	 * @param string $site
	 */
	private function siteExists ($site) {
		return file_exists(DOCROOT . "Sites/$site/$site.info");
	}

	/**
	 * Grabs the data found in this site
	 */
	private function grabInfoFileData () {
		$data = extractKeyValArray(file_get_contents(DOCROOT . "Sites/{$this->site}/{$this->site}.info"));

		# Turn extends into array
		if (isset($data['extends'])) {
			$data['extends'] = tunnArray(explode(' ', trim($data['extends'])));
		}

		# Add potential thumbnail
		foreach (array('jpg', 'png', 'gif') as $ext) {
			$thumbPath = "Sites/{$this->site}/{$this->site}.$ext";

			if (file_exists(DOCROOT . $thumbPath)) {
				$data['thumb']['webpath'] = WEBROOT . $thumbPath;
				$data['thumb']['docpath'] = DOCROOT . $thumbPath;

				break;
			}
		}

		# Always set name to Site name
		$data['name'] = $this->site;

		# Make sure title is set
		if (!isset($data['title'])) {
			$data['title'] = $data['name'];
		}

		# Urlize name
		$data['slug'] = urlize($data['name']);

		$this->info = $data;
	}

	/**
	 * Includes all plugins for the selected sites
	 */
	private function includePlugins () {
		$pluginsPath = DOCROOT . 'Sites/{' . implode(',', $this->sites) . '}/Plugins/*/*.php';

		foreach (glob($pluginsPath, GLOB_BRACE) as $plugin) {
			$pluginName = basename(substr($plugin, 0, -4));

			if (!function_exists('SP' . $pluginName)) {
				include $plugin;

				Logger::log("Plugin \"$pluginName\" has been loaded.", __METHOD__);
			}
			else {
				Logger::log("Plugin \"$pluginName\" was already loaded.", __METHOD__, Logger::WARNING);
			}
		}
	}

	/**
	 * Returns the sites that make up this site
	 */
	public function getSites () {
		return $this->sites;
	}

	/**
	 * Returns the primary site
	 */
	public function getSite () {
		return $this->site;
	}

	/**
	 * Returns data found in SiteName.info file
	 */
	public function getInfo () {
		return $this->info;
	}

	/**
	 * Returns the path to an XML $pageName (ie 'Home') for this site
	 *
	 * @param string $pageName
	 */
	public function getPagePath ($pageName) { # TODO: Should check Styles/ as well
		static $pagePaths = array();

		if (isset($pagePaths[$pageName])) {
			return $pagePaths[$pageName];
		}
		else {
			$pageName = basename($pageName);

			foreach ($this->sites as $site) {
				$path = DOCROOT . "Sites/$site/Pages/$pageName.xml";

				if (file_exists($path)) {
					return $pagePaths[$pageName] = $path;
				}
			}
		}

		return false;
	}

	/**
	 * Returns $module's class name (PostComment => Blog_PostCommentModule)
	 *
	 * @param string module
	 */
	public function getModuleClassName ($module) {
		static $moduleClassNames = array();

		if (isset($moduleClassNames[$module])) {
			return $moduleClassNames[$module];
		}

		$sites = $this->sites;

		foreach ($sites as $site) {
			$path = DOCROOT . "Sites/$site/Modules/$module/{$module}Module.php";

			if (file_exists($path)) {
				return $moduleClassNames[$module] = "{$site}_{$module}Module";
			}
		}

		return false;
	}

	/**
	 * Returns $module's .info-file path
	 *
	 * @param string module
	 */
	public function getModuleInfoPath ($module) {
		static $moduleInfoPaths = array();

		if (isset($moduleInfoPaths[$module])) {
			return $moduleInfoPaths[$module];
		}

		$sites = $this->sites;

		foreach ($sites as $site) {
			$path = DOCROOT . "Sites/$site/Modules/$module/{$module}.info";

			if (file_exists($path)) {
				return $moduleInfoPaths[$module] = $path;
			}
		}

		return false;
	}

	/**
	 * Returns an associative array of all the sites' configs
	 */
	public function getConfigs () {
		static $configs = array();

		if (count($configs)) {
			return $configs;
		}

		$sitesReversed = array_reverse($this->sites);

		foreach ($sitesReversed as $site) {
			$path = DOCROOT . "Sites/$site/Config.php";

			if (file_exists($path)) {
				$configs = array_merge($configs, include $path);
			}

			$moduleConfigs = glob(DOCROOT . "Sites/$site/Modules/*/Config.php");

			foreach ($moduleConfigs as $moduleConfig) {
				$configs = array_merge($configs, include $moduleConfig);
			}
		}

		return $configs;
	}

	/**
	 * Returns all the routes from the selected sites
	 */
	public function getRoutes () {
		static $routes = array();

		if (count($routes)) {
			return $routes;
		}

		$sitesReversed = array_reverse($this->sites);

		foreach ($sitesReversed as $site) {
			$path = DOCROOT . "Sites/$site/Routes.php";

			if (file_exists($path)) {
				$routes = array_merge($routes, include $path);
			}
		}

		return $routes;
	}

	/**
	 * Returns all the langs from the selected sites
	 */
	public function getLangs () {
		static $langs = array();

		if (count($langs)) {
			return $langs;
		}

		$sitesReversed = array_reverse($this->sites);

		foreach ($sitesReversed as $site) {
			$allLangs = array_merge(glob(DOCROOT . "Sites/$site/Lang/*.php"), glob(DOCROOT . "Sites/$site/Modules/*/Lang/*.php"));

			foreach ($allLangs as $lang) {
				$cc			= substr(basename($lang), 0, -4);
				$langs[$cc]	= isset($langs[$cc]) ? $langs[$cc] : array();
				$langs[$cc]	= array_merge($langs[$cc], include $lang);
			}
		}

		return $langs;
	}

	/**
	 * Displays one or more $modules wrapped in Base
	 * except on ajax-requests where they're wrapped in nothing (an un-named Wrapper)
	 *
	 * @param array $modules
	 */
	public function renderModules (array $modules, $inBase = true, $params = array()) {
		$xmlPage = '<?xml version="1.0" encoding="UTF-8"?>';
		$xmlPage .= $inBase ? '<Base>' : '<Wrapper type="none">';

		foreach ($modules as $module) {
			$xmlPage .= "<$module>";
		}

		$xmlPage .= $inBase ? '</Base>' : '</Wrapper>';

		return $this->renderPage($xmlPage, $params);
	}

	/**
	 * Displays an entire page (XML $page full of modules)
	 * Can be either name of page or valid XML
	 *
	 * @param string $page
	 */
	public function renderPage ($page, $params = array()) {
		Logger::log("Rendering page \"$page\".", __METHOD__);

		if ($page == FOF_PAGE) {
			header('HTTP/1.1 404 Not Found');
		}

		$output	= '';
		$path	= $this->getPagePath($page);

		if ($path) {
			$pageXML = file_get_contents($path);

			if (!$pageXML) {
				throw new Exception("The requested page \"$page\" appears to be empty.");
			}
		}
		else {
			throw new Exception("The requested page \"$page\" could not be found in your site or any of the sites your site extends \"" . implode(', ', SleekPHP::site()->getSites()) . "\".");
		}

		SleekPHP::$currentPage = $page;

		# Create and run the page
		$page = new Page($pageXML);

		# Make sure user is authorized to view page
		if (User()->authLevel() < $page->authLevel()) {
			Logger::log('User not authorized to view this page, rendering ' . LOGIN_PAGE . ' page instead.', __METHOD__);

			$output = $this->renderPage(NOT_AUTH_PAGE);
		}
		else {
			$page->init();

			Logger::log('Modules have been created, now running.', __METHOD__);

			$abortPage = $page->run($params);

			# Modules may return a new page to render, if a string isn't returned from running the page we continue
			if (!is_string($abortPage)) {
				Logger::log('Modules have run, now rendering.', __METHOD__);

				# Set style to page's style
				if ($newStyle = $page->getStyle()) {
					SleekPHP::setStyle($newStyle);
				}

				$output = $page->render();
			}
			else {
				Logger::log("Some module wanted to run the $abortPage page instead.", __METHOD__);

				$output = $this->renderPage($abortPage);
			}
		}

		return $output;
	}

	public function hasPage ($page) {
		return $this->getPagePath($page);
	}

	/**
	 * ...
	 *
	 * @param string $style
	 */
	public function hasStyle ($style) {
		$style = basename($style);

		static $styles = array();

		if (isset($styles[$style])) {
			return $styles[$style];
		}
		else {
			foreach ($this->sites as $site) {
				$path = DOCROOT . "Sites/$site/Styles/$style/$style.info";

				if (file_exists($path)) {
					return $styles[$style] = $path;
				}
			}
		}

		return false;
	}
}
